/* -- INCLUDES ------------------------------------------------------------- */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdarg.h>
#include <semaphore.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>

#include "config.h"

/* -- PRIVATE VARIABLES ---------------------------------------------------- */
static sem_t threads_count;
static FILE * global_logs;
static pthread_mutex_t logs_mutex = PTHREAD_MUTEX_INITIALIZER;

/* -- PUBLIC FUNCTIONS ----------------------------------------------------- */
int main( int argc, char ** argv )
{
	int i;
	unsigned int j;
	array_t filelist = {0, NULL};

	printf("[+] Getting file list...\n");
	if( argc <= 1 )
		get_file_list( &filelist, DEFAULT_FUZZ_DIR );
	else
		for( i=1; i<argc; i++ )
			get_file_list( &filelist, argv[i] );
	
	if( filelist.size == 0 ) {
		printf("[-] No file found\n");
	} else {
		printf("[+] Fuzzing with  %u files :\n", filelist.size);
		print_array( &filelist, NULL, "\t- %s\n" );

		global_logs = fopen(GLOBAL_LOGS_FILE, "w");
		if( global_logs == NULL ) {
			printf("[-] Cannot open global logs file (%s) : %s\n", GLOBAL_LOGS_FILE, strerror(errno));
		} else {
			logmsg( SUCCESS, global_logs, "Starting fuzzing session with %d file(s)", filelist.size );
			logmsg( INFO, global_logs, "Executable : %s", EXECUTABLE );
			sem_init( &threads_count, PTHREAD_PROCESS_PRIVATE, 0 );

			extend_core_limit();

			for( j=0; j<filelist.size; j++ ) {
				sem_post( &threads_count );
				launch_fuzz_thread( filelist.elements[j] );
			}

			while( sem_getvalue( &threads_count, &i ) == 0 && i > 0 ) {
				printf("[.] %d thread(s) remaining...\n", i);
				sleep( WAIT_TIME );
			}

			sem_destroy( &threads_count );
			logmsg( SUCCESS, global_logs, "Ending fuzzing session" );
			fclose( global_logs );
		}
	}

	array_free( &filelist );
	printf("[+] Done\n");
	return EXIT_SUCCESS;
}

/* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
static void print_array( array_p array, char * text, char * frmt )
{
	unsigned int i;
	char * format = (frmt != NULL ? frmt : DEFAULT_FORMAT );

	if( text != NULL )
		printf("%s", text);

	for( i=0; i<array->size; i++ )
		printf(format, array->elements[i]);
}

static void array_append( array_p array, char * value )
{
	if( array->size % DELTA_ARRAY == 0 ) {
		array->elements = realloc( array->elements, (array->size + DELTA_ARRAY)*sizeof(char *) );
		if( array->elements == NULL ) {
			printf("[-] realloc error while appending to array\n");
			exit(EXIT_FAILURE);
		}
	}

	array->elements[ array->size++ ] = strdup(value);
}

static void array_free( array_p array )
{
	unsigned int i;

	for( i=0; i<array->size; i++ )
		free( array->elements[i] );
	free( array->elements );
}

static void get_file_list( array_p list, char * path )
{
	struct stat stats;
	DIR *dir;
	struct dirent *entry;
	char * name;
	int delta;

	if( stat(path, &stats) != 0 ) {
		printf("\t- %s : %s\n", path, strerror(errno));
	} else if( S_ISREG(stats.st_mode) ) {
		array_append(list, path);
	} else if( S_ISDIR(stats.st_mode) ) {
		dir = opendir(path);
		if( dir == NULL ) {
			printf("\t- %s : %s\n", path, strerror(errno));
		} else {
			while( (entry = readdir(dir)) != NULL ) {
				if( entry->d_type == DIR_ENTRY_FILE ) {
					delta = strlen(entry->d_name) - strlen(DEFAULT_EXTENSION);
					if( delta > 0 && strcmp(entry->d_name + delta, DEFAULT_EXTENSION) == 0 ) {
						name = malloc( strlen(path) + 1 + strlen(entry->d_name) + 1 );
						if( name == NULL ) {
							printf("\t- %s/%s : malloc error\n", path, entry->d_name);
						} else {
							sprintf(name, "%s/%s", path, entry->d_name);
							array_append(list, name);
							free(name);
						}
					}
				}
			}
			closedir(dir);
		}
	} else {
		printf("\t- %s : invalid type of file\n", path);
	}
}

static void extend_core_limit()
{
	struct rlimit lim;

	if( getrlimit( RLIMIT_CORE, &lim ) != 0 ) {
		logmsg( WARNING, global_logs, "Cannot retreive core size limit" );
	} else if( lim.rlim_cur == RLIM_INFINITY ) {
			logmsg( INFO, global_logs, "Core size limit is already RLIM_INFINITY" );
	} else {
		lim.rlim_cur = lim.rlim_max;
		if( setrlimit( RLIMIT_CORE, &lim ) != 0 )
			logmsg( WARNING, global_logs, "Cannot extend core size limit" );
		else if( lim.rlim_max == RLIM_INFINITY )
			logmsg( INFO, global_logs, "Core size limit extended to RLIM_INFINITY" );
		else
			logmsg( INFO, global_logs, "Core size limit extended to %u", lim.rlim_max );
	}
}

static void launch_fuzz_thread( char * file )
{
	pthread_t thread;

	if( pthread_create( &thread, NULL, fuzz_file, (void *)file ) != 0 ) {
		logmsg( ERROR, global_logs, "Cannot fuzz file \"%s\" : %s\n", file, strerror(errno) );
		printf("[-] Cannot fuzz file \"%s\" : %s\n", file, strerror(errno));
		sem_wait( &threads_count );
	} else {
		logmsg( INFO, global_logs, "Fuzzing file %s (thread=%#08x)", file, (unsigned int)thread );
	}
}

static void logmsg( level_t type, FILE * f, char * msg, ... )
{
	/* TODO : global_logs ? */

	va_list vl;
	char indic;
	char date[ STR_DATE_SIZE + 1 ];
	struct tm * tm;
	struct timeval tv;
	struct timezone tz;

	switch( type ) {
		case INFO		: indic = '.'; break;
		case WARNING	: indic = '~'; break;
		case ERROR		: indic = '-'; break;
		case SUCCESS	: indic = '+'; break;
		case IMPORTANT	: indic = '!'; break;
		case NONE		:			   break;
	}

	/* sapu : un seul mutex pour de multiples fichiers... */
	pthread_mutex_lock( &logs_mutex );

	gettimeofday( &tv, &tz );
	tm = localtime( &tv.tv_sec );
	//strftime( date, STR_DATE_SIZE, STR_DATE_FORMAT, local );
	sprintf( date, STR_DATE_FORMAT, tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec/1000 );

	if( type != NONE )
		fprintf(f, "[%s][%c] ", date, indic);

	va_start(vl, msg);
	vfprintf(f, msg, vl);
	va_end(vl);

	if( type != NONE )
		fprintf(f, "\n");

	fflush(f);
	pthread_mutex_unlock( &logs_mutex );
}

static void * fuzz_file( void * filevoid )
{
	unsigned int line_number, length;
	char line[ LINE_SIZE + 1 ] = {0};
	char * filename, * file;
	FILE * logs, * fuzz;
	pid_t child, w;
	int status;
	struct user_regs_struct regs;
	siginfo_t siginfo;
	char * argv[] = {EXECUTABLE ARGV_START line ARGV_END NULL};
	char * envp[] = {NULL};

	file = (char *)filevoid;
	filename = malloc( strlen(file) + strlen(LOG_FILE_EXTENSION) + 1 );
	if( filename == NULL ) {
		printf("[-] Cannot create log for file \"%s\" : (malloc) %s\n", file, strerror(errno));
	} else {
		sprintf(filename, "%s%s", file, LOG_FILE_EXTENSION);
		logs = fopen( filename, "w" );
		if( logs == NULL ) {
			printf("[-] Cannot create log for file \"%s\" : (fopen) %s\n", file, strerror(errno));
		} else {
			logmsg( SUCCESS, logs, "Starting fuzzing session with file \"%s\"", file );
			logmsg( INFO, logs, "logging file created : \"%s\"", filename);
			
			fuzz = fopen( file, "r" );
			if( fuzz == NULL ) {
				logmsg( ERROR, logs, "Cannot open fuzzing file : (fopen) %s", file, strerror(errno) );
			} else {
				logmsg( INFO, logs, "Fuzzing file opened\n" );

				line_number = 0;
				while( fgets(line, LINE_SIZE, fuzz) != NULL ) {
					line_number++;
					length = strlen(line);
					if( line[ length - 1 ] != '\n' )
						logmsg( WARNING, logs, "[%03u] Line may have been truncated (get \"%c\" instead of \\n)", line_number, line[ length - 1 ] );
					line[ length - 1 ] = 0;
					logmsg( INFO, logs, "[%03u] Read line : %s", line_number, line );

					child = fork();
					if( child == -1 ) {
						logmsg( WARNING, logs, "[%03u] Cannot execute line : (fork) %s", line_number, strerror(errno));
					} else if( child == 0 ) {
						close(fileno(stdin));
						close(fileno(stdout));
						close(fileno(stderr));
						if( execve( EXECUTABLE, argv, envp) == -1 ) {
							logmsg( WARNING, logs, "[%03u] Cannot execute line : (execve) %s", line_number, strerror(errno));
							exit(EXIT_FAILURE);
						}
					} else {
						if( ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1 )
							logmsg( WARNING, logs, "[%03u] Cannot attach to child process : %s", line_number, strerror(errno));

						do {
							w = waitpid( child, &status, WUNTRACED | WCONTINUED );
							if( w == -1 ) {
								logmsg( WARNING, logs, "[%03u] Cannot get exit status : (waitpid) %s", line_number, strerror(errno));
								break;
							} else if( WIFEXITED(status) ) {
								logmsg( INFO, logs, "[%03u] Exited with status %d", line_number, WEXITSTATUS(status) );
							} else if( WIFSIGNALED(status)) {
								#ifdef WCOREDUMP
								if( WCOREDUMP(status) ) {
									char corename[ sizeof(CORE_DUMP_NAME) + 1 + 10  + 1 ] = {0};
									sprintf(corename, "%s.%d", CORE_DUMP_NAME, child);
									if( rename(CORE_DUMP_NAME, corename) == - 1 )
										logmsg( WARNING, logs, "[%03u] Core dump produced, but cannot rename it : %s", line_number, strerror(errno));
									else
										logmsg( IMPORTANT, logs, "[%03u] Core dump produced, and renamed in \"%s\"", line_number, corename);
								} else {
									logmsg( INFO, logs, "[%03u] No core dump produced", line_number );
								}
								#endif // WCOREDUMP

								logmsg( IMPORTANT, global_logs, "%s:%u received signal %s", file, line_number, signal_name(WTERMSIG(status)));
								logmsg( IMPORTANT, logs, "[%03u] Killed by signal %s", line_number, signal_name(WTERMSIG(status)) );

							} else if( WIFSTOPPED(status)) {
								logmsg( INFO, logs, "[%03u] Stopped by signal %s", line_number, signal_name(WSTOPSIG(status)) );
								if( ptrace( PTRACE_GETSIGINFO, child, NULL, &siginfo ) == -1 ) {
									logmsg( WARNING, logs, "[%03u] Failed to retreive signal infos : %s", line_number, strerror(errno) );
									break;
								} else if( siginfo.si_signo == SIGSEGV ) { /* TODO : other signals */
									logmsg( IMPORTANT, global_logs, "%s:%u segmentation fault at address %p", file, line_number, (void*)siginfo.si_band );
									logmsg( IMPORTANT, logs, "[%03u] Segmentation Fault at address %p", line_number, (void*)siginfo.si_band );
									if( ptrace(PTRACE_GETREGS, child, NULL, &regs) == -1 ) {
										logmsg( WARNING, logs, "[%03u] Cannot get registers : (getregs) %s", line_number, strerror(errno));
									} else {
										logmsg( IMPORTANT, logs, "[%03u] EAX=%08x, EBX=%08x, ECX=%08x, EDX=%08x EFLAGS=%08x", line_number, regs.eax, regs.ebx, regs.ecx, regs.edx, regs.eflags);
										logmsg( IMPORTANT, logs, "[%03u] ESI=%08x, EDI=%08x, EBP=%08x, ESP=%08x EIP=%08x", line_number, regs.esi, regs.edi, regs.ebp, regs.esp, regs.eip);
									}
									ptrace( PTRACE_DETACH, child, NULL, NULL );
									continue;
								} else {
									//logmsg( INFO, logs, "Continuing..." );
									ptrace( PTRACE_CONT, child, NULL, NULL );
								}
								/* ptrace( PTRACE_CONT, child, NULL, NULL ); */
							} else if( WIFCONTINUED(status)) {
								logmsg( INFO, logs, "[%03u] Continued...", line_number );
							}
						} while( !WIFEXITED(status) && !WIFSIGNALED(status) );
						
						ptrace(PTRACE_KILL, child, NULL, NULL);
					}
					logmsg( NONE, logs, "\n" );
				}
			}
			logmsg( SUCCESS, logs, "Ending fuzzing session", file );
		}
	}

	sem_wait( &threads_count );

	return NULL;
}

static char * signal_name( int signal )
{
	char unknow[ 30 ] = {0};
	
	switch( signal ) {
		case SIGHUP		: return "SIGHUP"		; /* 1 */
		case SIGINT 	: return "SIGINT"		; /* 2 */
		case SIGQUIT	: return "SIGQUIT"		; /* 3 */
		case SIGILL 	: return "SIGILL"		; /* 4 */
		case SIGTRAP	: return "SIGTRAP"		; /* 5 */
		case SIGABRT	: return "SIGABRT"		; /* 6 */
		case SIGBUS 	: return "SIGBUS"		; /* 7 */
		case SIGFPE		: return "SIGFPE"		; /* 8 */
		case SIGKILL 	: return "SIGKILL"		; /* 9 */
		case SIGUSR1	: return "SIGUSR1"		; /* 10 */
		case SIGSEGV	: return "SIGSEGV"		; /* 11 */
		case SIGUSR2	: return "SIGUSR2"		; /* 12 */
		case SIGPIPE	: return "SIGPIPE"		; /* 13 */
		case SIGALRM	: return "SIGALRM"		; /* 14 */
		case SIGTERM	: return "SIGTERM"		; /* 15 */
		case SIGSTKFLT	: return "SIGSTKFLT"	; /* 16 */
		case SIGCHLD	: return "SIGCHLD"		; /* 17 */
		case SIGCONT	: return "SIGCONT"		; /* 18 */
		case SIGSTOP	: return "SIGSTOP"		; /* 19 */
		case SIGTSTP	: return "SIGTSTP"		; /* 20 */
		case SIGTTIN	: return "SIGTTIN"		; /* 21 */
		case SIGTTOU	: return "SIGTTOU"		; /* 22 */
		case SIGURG		: return "SIGURG"		; /* 23 */
		case SIGXCPU	: return "SIGXCPU"		; /* 24 */
		case SIGXFSZ	: return "SIGXFSZ"		; /* 25 */
		case SIGVTALRM	: return "SIGVTALRM"	; /* 26 */
		case SIGPROF	: return "SIGPROF"		; /* 27 */
		case SIGWINCH	: return "SIGWINCH"		; /* 28 */
		case SIGIO		: return "SIG{POLL|IO}"	; /* 29 */
		case SIGPWR		: return "SIGPWR"		; /* 30 */
		case SIGUNUSED	: return "SIGUNUSED"	; /* 31 */
		default:
			sprintf(unknow, "Unknow signal %u", signal);
			return strdup(unknow);
	}
}

